import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.atomic.AtomicInteger;
import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationScope;

// java --add-opens java.base/jdk.internal.vm=ALL-UNNAMED Prime4
public final class Prime4 {
	private static final ContinuationScope cs = new ContinuationScope("");
	private static final VarHandle vsh = MethodHandles.arrayElementVarHandle(Object[].class);

	public static final class SpinLock extends AtomicInteger {
		public void lock() {
			//noinspection StatementWithEmptyBody
			while (!compareAndSet(0, 1))
				;
		}

		public void unlock() {
			set(0);
		}
	}

	public static final class LocalQueue<V> {
		private static final int CAPACITY = 0x8000;
		private static final int MASK = CAPACITY - 1;
		private final Object[] vs = new Object[CAPACITY];
		@SuppressWarnings("unused")
		private long d0, d1, d2, d3, d4, d5, d6;
		@SuppressWarnings("unused")
		private volatile long firstIndex;
		@SuppressWarnings("unused")
		private long d7, d8, d9, d10, d11, d12, d13;
		private long lastIndex;

		// single-thread pushLast & popLast
		public boolean pushLast(V v) {
			if (v == null)
				throw new NullPointerException();
			var i = lastIndex;
			if (!vsh.compareAndSet(vs, (int)i & MASK, (Object)null, v))
				return false;
			lastIndex = i + 1;
			return true;
		}

		public V popLast() {
			var i = lastIndex - 1;
			@SuppressWarnings("unchecked")
			V v = (V)vsh.getAndSet(vs, (int)i & MASK, (Object)null);
			if (v != null)
				lastIndex = i;
			return v;
		}

		public V popFirst() {
			for (var i0 = firstIndex; ; ) {
				@SuppressWarnings("unchecked")
				V v = (V)vsh.getAndSet(vs, (int)i0 & MASK, (Object)null);
				if (v != null) {
					firstIndex = i0 + 1;
					return v;
				}
				var i1 = firstIndex;
				if (i1 == i0)
					return null;
				i0 = i1;
			}
		}
	}

	public static final class GlobalQueue<V> {
		private static final int CAPACITY = 0x8000;
		private static final int MASK = CAPACITY - 1;
		private final SpinLock spinLock = new SpinLock();
		private final Object[] vs = new Object[CAPACITY];
		@SuppressWarnings("unused")
		private long d0, d1, d2, d3, d4, d5, d6;
		private long firstIndex;
		@SuppressWarnings("unused")
		private long d7, d8, d9, d10, d11, d12, d13;
		private long lastIndex;

		public boolean pushLast(V v) {
			if (v == null)
				throw new NullPointerException();
			spinLock.lock();
			var i0 = lastIndex;
			int i = (int)i0 & MASK;
			if (vs[i] != null) {
				spinLock.unlock();
				return false;
			}
			vs[i] = v;
			lastIndex = i0 + 1;
			spinLock.unlock();
			return true;
		}

		public V popFirst() {
			spinLock.lock();
			var i0 = firstIndex;
			@SuppressWarnings("unchecked")
			V v = (V)vs[(int)i0 & MASK];
			if (v != null)
				firstIndex = i0 + 1;
			spinLock.unlock();
			return v;
		}
	}

	public static final class WorkThread extends Thread {
		private static final VarHandle pausingHandle;
		private static final int THREADS = Runtime.getRuntime().availableProcessors();
		private static final WorkThread[] threads = new WorkThread[THREADS];
		private static final GlobalQueue<CoTask> globalQueue = new GlobalQueue<>();
		private final int idx;
		private final LocalQueue<CoTask> localQueue = new LocalQueue<>();

		static {
			var l = MethodHandles.lookup();
			try {
				pausingHandle = l.findVarHandle(CoTask.class, "pausing", boolean.class);
			} catch (ReflectiveOperationException e) {
				throw new RuntimeException(e);
			}
			for (int i = 0; i < THREADS; i++)
				threads[i] = new WorkThread(i);
			for (int i = 0; i < THREADS; i++)
				threads[i].start();
		}

		private WorkThread(int idx) {
			super("T" + idx);
			this.idx = idx;
		}

		public static void pause() {
			Continuation.yield(cs);
		}

		static void resume(CoTask c) {
			if ((boolean)pausingHandle.getAndSet(c, false))
				return;
			WorkThread t;
			var c0 = (CoTask)Continuation.getCurrentContinuation(cs);
			if (c0 != null && (t = c0.thread) != null && t.localQueue.pushLast(c)) // Thread.currentThread() is unreal for continuation
				return;
			if (!globalQueue.pushLast(c))
				throw new RuntimeException();
		}

		private CoTask steal() { // not good as ForkJoinPool
			for (int i = idx; ; ) {
				if (++i >= THREADS)
					i = 0;
				if (i == idx)
					return null;
				var c = threads[i].localQueue.popFirst();
				if (c != null)
					return c;
			}
		}

		@Override
		public void run() {
			try {
				//noinspection InfiniteLoopStatement
				for (; ; ) {
					var c = localQueue.popLast();
					if (c == null) {
						c = globalQueue.popFirst();
						if (c == null) {
							c = steal();
							if (c == null)
								continue; // or waiting?
						}
					}
					c.thread = this;
					do
						c.run();
					while (!(boolean)pausingHandle.getAndSet(c, false));
					c.thread = null;
				}
			} catch (Throwable e) {
				//noinspection CallToPrintStackTrace
				e.printStackTrace();
			}
		}
	}

	static final class CoTask extends Continuation {
		int chanValue;
		CoTask waitNext;
		boolean pausing;
		WorkThread thread;

		CoTask(Runnable target) {
			super(cs, target);
		}
	}

	static final class IntChan {
		private final SpinLock spinLock = new SpinLock();
		private CoTask sendWaitHead;
		private CoTask recvWaitHead;

		void send(int v) {
			spinLock.lock();
			var c = recvWaitHead;
			if (c != null) {
				recvWaitHead = c.waitNext;
				spinLock.unlock();
				c.chanValue = v;
				WorkThread.resume(c);
				return;
			}
			c = (CoTask)Continuation.getCurrentContinuation(cs);
			c.chanValue = v;
			c.waitNext = sendWaitHead;
			c.pausing = true;
			sendWaitHead = c;
			spinLock.unlock();
			WorkThread.pause();
		}

		int recv() {
			spinLock.lock();
			var c = sendWaitHead;
			if (c != null) {
				sendWaitHead = c.waitNext;
				spinLock.unlock();
				var v = c.chanValue;
				WorkThread.resume(c);
				return v;
			}
			c = (CoTask)Continuation.getCurrentContinuation(cs);
			c.waitNext = recvWaitHead;
			c.pausing = true;
			recvWaitHead = c;
			spinLock.unlock();
			WorkThread.pause();
			return c.chanValue;
		}
	}

	static void go(Runnable r) {
		WorkThread.resume(new CoTask(r));
	}

	static void generate(IntChan ch) {
		//noinspection InfiniteLoopStatement
		for (int i = 2; ; i++)
			ch.send(i);
	}

	static void filter(IntChan in, IntChan out, int prime) {
		//noinspection InfiniteLoopStatement
		for (; ; ) {
			var i = in.recv();
			if (i % prime != 0)
				out.send(i);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		var count = args.length > 0 ? Integer.parseInt(args[0]) : 10000;
		go(() -> {
			var ch = new IntChan();
			var ch0 = ch;
			go(() -> generate(ch0));
			for (int i = 0; ; ) {
				var prime = ch.recv();
				if (++i == count) {
					System.out.println(prime);
					System.exit(0);
				}
				var ch1 = ch;
				var ch2 = new IntChan();
				go(() -> filter(ch1, ch2, prime));
				ch = ch2;
			}
		});
		Thread.sleep(1_000_000L);
	}
}

//	private final ConcurrentLinkedQueue<Thread> waitQueue = new ConcurrentLinkedQueue<>();
//	void lock() {
//		if (!compareAndSet(0, 1)) {
//			waitQueue.offer(Thread.currentThread());
//			var t = Thread.currentThread();
//			LockSupport.park();
//			if (t != Thread.currentThread()) {
//				System.out.println("ERROR9: " + t + ", " + Thread.currentThread());
//			}
//		}
//	}
//
//	void unlock() {
//		for (; ; ) {
//			var t = waitQueue.poll();
//			if (t != null)
//				LockSupport.unpark(t);
//			else {
//				set(0);
//				if (!waitQueue.isEmpty() && compareAndSet(0, 1))
//					continue;
//			}
//			break;
//		}
//	}
